package org.xqh.utils.encrypt;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;

/**
 * @ClassName AESUtils
 * @Description AES加解密
 * @Author xuqianghui
 * @Date 2020/4/22 11:33
 * @Version 1.0
 */

@Slf4j
public class AESUtils {


    public static void main12333122(String[] args) throws UnsupportedEncodingException {
        System.out.println(EncryptUtils.getMd5("ak=DH00002006000029&company=ugreen&count=10&err=true&mac=98%3A6E%3AE8%3A20%3A25%3A03&name=fff&searchKey=%E7%BB%BF%E8%81%94&sn=devicesn&ts=1640835564&SecretKey=secret_key_112233ffgghh"));
        String text = "听说nas卖了100万台听说nas卖了100万台听说nas卖了100万台听说nas卖了100万台听说nas卖了100万台听说nas卖了100万台听说nas卖了100万台";//内容
        String key = "ugreen@nasdamai#";//密码
        String iv = "18o31yigkrp14459";//偏移量
        String ecbResult = encryptToBase64(text, key, EncryptConstants.ALGORITHM_ECB_PKCS5);
        String cbcResult = encryptToBase64(text, key, EncryptConstants.ALGORITHM_CBC_PKCS5, iv);
//
        System.out.println("ECB加密结果: " + ecbResult);
        System.out.println("CBC加密结果: " + cbcResult);
//
        String ecbDecryptResult = decryptFromBase64(ecbResult, key, EncryptConstants.ALGORITHM_ECB_PKCS5);
        String cbcDecryptResult = decryptFromBase64(cbcResult, key, EncryptConstants.ALGORITHM_CBC_PKCS5, iv);
//
        System.out.println("ECB解密结果: " + ecbDecryptResult);
        System.out.println("CBC解密结果: " + cbcDecryptResult);

        String str = "这里有几个汉字...";
        System.out.println(base64Encode(str.getBytes(EncryptConstants.CHAR_ENCODING)));
        String encodeStr = EncryptUtils.base64Encode(str);
        System.out.println(encodeStr);
        System.out.println(EncryptUtils.base64Decode(encodeStr));

    }

    public static void main2(String[] args) {
        int a = 100;
        int b = 120;
        System.out.println(Math.abs(a-b));
    }

    public static void main123(String[] args) throws Exception {
//        String cbcResult = "CiyLU1Aw2KjvrjMdj8YKliAjtP4gsMZMQmRzooG2xrDcvSnxIMXFufNstNGTyaGS9uT5geRa0W4oTOb1WT7fJlAC+oNPdbB+3hVbJSRgv+4lGOETKUQz6OYStslQ142dNCuabNPGBzlooOmB231qMM85d2/fV6ChevvXvQP8Hkue1poOFtnEtpyxVLW1zAo6/1Xx1COxFvrc2d7UL/lmHInNlxuacJXwu0fjpXfz/YqYzBIBzD6WUfTIF9GRHpOn/Hz7saL8xz+W//FRAUid1OksQaQx4CMs8LOddcQhULW4ucetDf96JcR3g0gfRK4PC7E/r7Z6xNrXd2UIeorGj5Ef7b1pJAYB6Y5anaHqZ9J6nKEBvB4DnNLIVWSgARns/8wR2SiRS7MNACwTyrGvt9ts8p12PKFdlqYTopNHR1Vf7XjfhQlVsAJdNiKdYmYVoKlaRv85IfVunYzO0IKXsyl7JCUjCpoG20f0a04COwfneQAGGwd5oa+T8yO5hzuyDb/XcxxmK01EpqOyuxINew==";
//        String key = RSAUtils.base64DecodeStr("tiihtNczf5v6AKRyjwEUhQ==");
//        String iv = RSAUtils.base64DecodeStr("r7BXXKkLb8qrSNn05n0qiA==");
////        System.out.println(RSAUtils.base64DecodeStr(cbcResult));
//        String cbcDecryptResult = decryptWXAppletInfo(key, cbcResult, iv);
//        System.out.println(cbcDecryptResult);



    }

    public static void main111111111111111111(String[] args) throws Exception {
        String encryptData = "R+pOgkKbdKP4iEJiDuelE+u2AxDQ4QM+stjAKVRI1xdy5OJ8wgcm43Pe9ckmV0etUMsf5WfQ8UMnqmxHoiIBj6qMAUDlwkuwL7wtphJ0sKUzq/mAValjmrkb9/l2Q3vmQKZel5I5FJcnJDWd1Ew7/YS692Wafube9u4Uc5Huoap+JhpoDbqt1BGNlDMZVjU8NC2ihRetEFuV8VeTe9VzChtaclgXrAHsKfcpPjGiLEkYdZYJX61y1sYY3Moxb67w8nAV5nNPgLC1TFbiV7HXUCLO7t5wf5VgKinjSL2cJg4PArFXGr/tmEpaw21eCIgZ+wIH/DJICFOwNiJ8XHI0XRhHS77VhbQfb7H4FA8CGZpHb+u5AD5hb329OTC1jNiuTkbHkPb3HkOpu3+VMjmdYGQ+vCk/blIWy9+bBOIHwdU=";
        String sessionKey = "p9e6RMHnRT3Ujmjhm+mIPw==";
        String iv = "nWFKKPx2I5Fur3HZwq4u0A==";

        System.out.println(decryptWXAppletInfo(sessionKey, encryptData, iv));
    }

    /**
     * 解密微信小程序 数据
     * @param sessionKey
     * @param encryptedData
     * @param iv
     * @return
     * @throws Exception
     */
    public static String decryptWXAppletInfo(String sessionKey, String encryptedData, String iv)
            throws Exception {
        byte[] encryptData = org.apache.commons.codec.binary.Base64.decodeBase64(encryptedData);
        byte[] ivData = org.apache.commons.codec.binary.Base64.decodeBase64(iv);
        byte[] sessionKeyB = org.apache.commons.codec.binary.Base64.decodeBase64(sessionKey);
        KeyGenerator keygen= KeyGenerator.getInstance("AES");
        keygen.init(128);
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivData);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(sessionKeyB, "AES");
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        byte[] doFinal = cipher.doFinal(encryptData);
        return new String(doFinal);
    }


    /**
     * 指定 加密类型
     *
     * @param data
     * @param key
     * @param algorithm
     * @return
     */
    public static byte[] encrypt(byte[] data, byte[] key, String algorithm, String iv) {
        if (key.length != 16) {
            throw new RuntimeException("Invalid AES key length (must be 16 bytes)");
        }
        return calcResult(data, key, Cipher.ENCRYPT_MODE, algorithm, iv);
    }

    public static byte[] decrypt(byte[] data, byte[] key, String algorithm, String iv) {
        if (key.length != 16) {
            throw new RuntimeException("Invalid AES key length (must be 16 bytes)");
        }
        return calcResult(data, key, Cipher.DECRYPT_MODE, algorithm, iv);
    }

    /**
     * 加/解密 计算结果
     *
     * @param data      内容
     * @param key       密码
     * @param model     加/解密
     * @param algorithm 算法类型
     * @param iv     偏移量  CBC模式 需要
     * @return
     */
    public static byte[] calcResult(byte[] data, byte[] key, int model, String algorithm, String iv) {
        try {
            SecretKeySpec secretKey = new SecretKeySpec(key, EncryptConstants.ENCRYPT_AES);
            byte[] enCodeFormat = secretKey.getEncoded();
            SecretKeySpec secKey = new SecretKeySpec(enCodeFormat, EncryptConstants.ENCRYPT_AES);
            Cipher cipher = Cipher.getInstance(algorithm);// 创建密码器
            if (StringUtils.hasText(iv) && algorithm.contains("/CBC/")) {
                //使用CBC模式，需要一个向量iv，可增加加密算法的强度
                IvParameterSpec ivPs = new IvParameterSpec(iv.getBytes(EncryptConstants.CHAR_ENCODING));
                cipher.init(model, secKey, ivPs);// 初始化
            }else {
                cipher.init(model, secKey);
            }
            byte[] result = cipher.doFinal(data);
            return result;
        } catch (Exception e) {
            throw new RuntimeException("decrypt fail!", e);
        }
    }

    /**
     * 指定算法类型加密
     *
     * @param data
     * @param key
     * @param algorithm
     * @return
     */
    public static String encryptToBase64(String data, String key, String algorithm, String iv) {
        try {
            byte[] valueByte = encrypt(data.getBytes(EncryptConstants.CHAR_ENCODING), key.getBytes(EncryptConstants.CHAR_ENCODING), algorithm, iv);
            return base64Encode(valueByte);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("encrypt fail!", e);
        }
    }

    /**
     * 指定算法类型加密
     *
     * @param data
     * @param key
     * @param algorithm
     * @return
     */
    public static String encryptToBase64(String data, String key, String algorithm) {
        return encryptToBase64(data, key, algorithm, null);
    }

    /**
     * 指定 算法类型 解密
     *
     * @param data
     * @param key 密码 (utf-8格式)
     * @param algorithm
     * @param iv  偏移量
     * @return
     */
    public static String decryptFromBase64(String data, String key, String algorithm, String iv) {
        try {
            byte[] originalData = base64Decode(data);
            byte[] valueByte = decrypt(originalData, key.getBytes(EncryptConstants.CHAR_ENCODING), algorithm, iv);
            return new String(valueByte, EncryptConstants.CHAR_ENCODING);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("decrypt fail!", e);
        }
    }

    public static String decryptFromBase64(String data, String key, String algorithm) {
        return decryptFromBase64(data, key, algorithm, null);
    }

    /**
     * 解密
     * @param data
     * @param key 密码 (base64格式)
     * @param algorithm
     * @param iv
     * @return
     */
    public static String decryptWithKeyBase64(String data, String key, String algorithm, String iv) {
        try {
            byte[] originalData = base64Decode(data);
            byte[] valueByte = decrypt(originalData, base64Decode(key), algorithm, iv);
            return new String(valueByte, EncryptConstants.CHAR_ENCODING);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("decrypt fail!", e);
        }
    }

    public static String decryptWithKeyBase64(String data, String key, String algorithm){
        return decryptWithKeyBase64(data, key, algorithm, null);
    }

    public static byte[] generateRandomKey() {
        KeyGenerator keygen = null;
        try {
            keygen = KeyGenerator.getInstance(EncryptConstants.ALGORITHM_CBC_PKCS5);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(" generateRandomKey fail!", e);
        }
        SecureRandom random = new SecureRandom();
        keygen.init(random);
        Key key = keygen.generateKey();
        return key.getEncoded();
    }

    public static String generateRandomKeyWithBase64() {
        return base64Encode(generateRandomKey());
    }

    public static String base64Encode(byte[] data) {
        return java.util.Base64.getEncoder().encodeToString(data);
    }

    public static byte[] base64Decode(String key) {
        return java.util.Base64.getDecoder().decode(key);
    }


    private static final int AES_KEY_SIZE = 256;
    private static final int GCM_TAG_LENGTH = 128; // 16 bytes
    private static final int GCM_IV_LENGTH = 128;  // 16 bytes

    private static final int KEY_SIZE = 256;
    private static final int TAG_LENGTH = 128;

    public static void main(String[] args) throws Exception {
        // 生成密钥
//        SecretKey key = generateKey();
        String key = "0123456789012345678901234567890a";

        // 待加密的原始数据
        String originalText = "Hello, World of AES-GCM123rffggg!";

        // 加密
        String ciphertext = encryptWithGcm(originalText, key);
        System.out.println("Ciphertext: " + ciphertext);

        // 解密
        String decryptedtext = decryptForGcm(ciphertext, key);
        System.out.println("Decrypted text: " + decryptedtext);
    }

//    public static void main(String[] args) throws Exception {
//        // 密钥（必须是 16, 24 或 32 字节长）
//        byte[] keyBytes = "678901234567890a".getBytes(StandardCharsets.UTF_8);
////        byte[] keyBytes = "0123456789012345678901234567890a".getBytes(StandardCharsets.UTF_8);
//        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
//
//        // Nonce（必须是 12 字节长）
//        byte[] nonce = new byte[12];
//        new SecureRandom().nextBytes(nonce);
//
//        // GCMParameterSpec 需要 tag 长度和 nonce
//        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, nonce);
//
//        // 初始化 Cipher
//        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
//        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);
//
//        // 要加密的数据
//        String plaintext = "黄河之水天上来";
//        byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
//
//        // 输出加密后的数据和 nonce（以 Base64 编码为例）
//        System.out.println("Nonce (Base64): " + base64Encode(nonce));
//        System.out.println("Encrypted (Base64): " + base64Encode(encrypted));
//
//        // 注意：在实际应用中，你还需要将 tag 发送给解密方，但在这个例子中我们假设它是与加密数据一起发送的
//    }

    // 生成AES密钥
    public static SecretKey generateKey() throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(EncryptConstants.ENCRYPT_AES);
        keyGenerator.init(EncryptConstants.AES_KEY_SIZE, new SecureRandom());
        return keyGenerator.generateKey();
    }

    /**
     * 注意使用gmc模式 时如果报错 提示 256秘钥长度不支持 需要替换
     * @param msg
     * @param key
     * @return
     * @throws Exception
     */
    public static String encryptWithGcm(String msg, String key) throws Exception {
        return encryptWithGcm(msg, new SecretKeySpec(key.getBytes(), EncryptConstants.ENCRYPT_AES));
    }

    // AES-GCM加密
    public static String encryptWithGcm(String msg, SecretKey key) throws Exception {
        byte[] plaintext = msg.getBytes(StandardCharsets.UTF_8);
        Cipher cipher = Cipher.getInstance(EncryptConstants.ALGORITHM_AES_GCM);
        byte[] iv = new byte[EncryptConstants.GCM_IV_LENGTH / 8];
        new SecureRandom().nextBytes(iv);
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(EncryptConstants.GCM_TAG_LENGTH, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, gcmParameterSpec);

        byte[] ciphertext = cipher.doFinal(plaintext);

        // 将IV和密文组合起来（在实际应用中，你还需要安全地传输或存储IV）
        byte[] encryptedMessage = new byte[ciphertext.length + iv.length];
        System.arraycopy(ciphertext, 0, encryptedMessage, 0, ciphertext.length);
        System.arraycopy(iv, 0, encryptedMessage, ciphertext.length, iv.length);
        return base64Encode(encryptedMessage);
    }

    public static String decryptForGcm(String msg, String key) throws Exception {
        return decryptForGcm(msg, new SecretKeySpec(key.getBytes(), EncryptConstants.ENCRYPT_AES));
    }

    // AES-GCM解密
    public static String decryptForGcm(String encryptedMessage, SecretKey key) throws Exception {
        byte[] iv = new byte[EncryptConstants.GCM_IV_LENGTH / 8];
        byte[] message = base64Decode(encryptedMessage);
        byte[] ciphertext = new byte[message.length - iv.length];

        // 分离IV和密文
        System.arraycopy(message, message.length - iv.length, iv, 0, iv.length);
        System.arraycopy(message, 0, ciphertext, 0, ciphertext.length);

        Cipher cipher = Cipher.getInstance(EncryptConstants.ALGORITHM_AES_GCM);
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(EncryptConstants.GCM_TAG_LENGTH, iv);
        cipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec);

        return new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
    }

    /**
     * 注意使用gmc模式 时如果报错 提示 256秘钥长度不支持 需要替换
     * @param msg
     * @param key
     * @return
     * @throws Exception
     */
    public static String encryptWithGcm(String msg, String key, int ivLen){
        return encryptWithGcm(msg, new SecretKeySpec(key.getBytes(), EncryptConstants.ENCRYPT_AES), ivLen);
    }

    // AES-GCM加密
    public static String encryptWithGcm(String msg, SecretKey key, int ivLen){
        try {
            byte[] plaintext = msg.getBytes(StandardCharsets.UTF_8);
            Cipher cipher = Cipher.getInstance(EncryptConstants.ALGORITHM_AES_GCM);
            byte[] iv = new byte[ivLen];
            new SecureRandom().nextBytes(iv);
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(EncryptConstants.GCM_TAG_LENGTH, iv);
            cipher.init(Cipher.ENCRYPT_MODE, key, gcmParameterSpec);

            byte[] ciphertext = cipher.doFinal(plaintext);

            byte[] encryptedMessage = new byte[ciphertext.length + iv.length];
            System.arraycopy(ciphertext, 0, encryptedMessage, 0, ciphertext.length);
            System.arraycopy(iv, 0, encryptedMessage, ciphertext.length, iv.length);
            return base64Encode(encryptedMessage);
        }catch (Exception e){
            log.error("encrypt msg: {} err: {}.", msg, e.getMessage());
            throw new RuntimeException("encrypt with aes gcm err");
        }
    }

    public static String decryptForGcm(String msg, String key, int ivLen) throws Exception {
        return decryptForGcm(msg, new SecretKeySpec(key.getBytes(), EncryptConstants.ENCRYPT_AES), ivLen);
    }

    // AES-GCM解密
    public static String decryptForGcm(String encryptedMessage, SecretKey key, int ivLen) throws Exception {
        byte[] iv = new byte[ivLen];
        byte[] message = base64Decode(encryptedMessage);
        byte[] ciphertext = new byte[message.length - iv.length];

        // 分离IV和密文
        System.arraycopy(message, message.length - iv.length, iv, 0, iv.length);
        System.arraycopy(message, 0, ciphertext, 0, ciphertext.length);

        Cipher cipher = Cipher.getInstance(EncryptConstants.ALGORITHM_AES_GCM);
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(EncryptConstants.GCM_TAG_LENGTH, iv);
        cipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec);

        return new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
    }
}
